package net.media.datafw.transport;

import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.media.datafw.DataException;
import net.media.datafw.transport.TransportException;
import net.media.datafw.transport.model.TransportField;
import net.media.datafw.transport.model.Transportable;
import net.media.datafw.utils.ReflectionUtil;

public abstract class Transporter {
	Logger logger = LoggerFactory.getLogger(getClass());
	public static HashMap<Class<?>, ArrayList<TransportField>> classFields = new HashMap<Class<?>, ArrayList<TransportField>>();
	public static HashMap<String, Class<?>> classNames = new HashMap<String, Class<?>>();
	public static HashMap<Class<?>, String> namesClass = new HashMap<Class<?>, String>();

	protected SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EEE dd HH:mm:ss zzz yyyy");

	@SuppressWarnings("unchecked")
	public static void registerClass(Class<?> clazz, String name) throws DataException {
		if (clazz != null) {
			ArrayList<TransportField> fields = (ArrayList<TransportField>) ReflectionUtil.getFields(clazz, TransportField.class);
			classFields.put(clazz, fields);
			registerClassNames(clazz, name);
		}
	}

	private static void registerClassNames(Class<?> clazz, String name) {
		classNames.put(name, clazz);
		namesClass.put(clazz, name);
	}

	public byte[] serialize(Object data) throws TransportException {
		Object serilizedData = null;

		Class<?> clazz = data.getClass();
		if (clazz.isArray()) {
			serilizedData = initialiseTrasportableArray();
			writeTransportableArray((Transportable[]) data, serilizedData);
		} else {
			serilizedData = initialiseTrasportable();
			writeTransportable((Transportable) data, serilizedData);
		}

		return stringify(serilizedData);
	}

	protected abstract byte[] stringify(Object serilizedData) throws TransportException;

	protected abstract Object initialiseTrasportableArray() throws TransportException;

	protected abstract Object initialiseTrasportable() throws TransportException;

	protected abstract void writeTransportable(Transportable data, Object serilizedData) throws TransportException;

	protected abstract void writeTransportableArray(Transportable[] data, Object serilizedData) throws TransportException;

	protected void serializeTransportable(Transportable objectData, Object serializedData) throws TransportException {
		if (objectData != null) {
			ArrayList<TransportField> fields = classFields.get(objectData.getClass());
			if (fields != null) {
				for (int fieldIdx = 0; fieldIdx < fields.size(); fieldIdx++) {
					TransportField field = fields.get(fieldIdx);
					if (field.doTransport()) {
						Object value = field.getValue(objectData);

						if (value != null/* && isprimitiveAndHasValue(field, value)*/) {
							onSerializeField(field, serializedData);
							serializeField(field, value, serializedData);
						}
					}
				}
				afterSerializeField(fields, serializedData);
			}
		}
	}

	private void serializeField(TransportField field, Object objectData, Object serializedData) throws TransportException {
		Type clazz = field.getType();
		if (!(clazz instanceof ParameterizedType) && !(clazz instanceof GenericArrayType) && ((Class<?>) clazz).isArray()) {
			clazz = ((Class<?>) clazz).getComponentType();
		} else if (clazz instanceof GenericArrayType) {
			clazz = ((GenericArrayType) clazz).getGenericComponentType();
		}

		if (clazz.equals(boolean.class) || clazz.equals(Boolean.class)) {
			serializeBoolean(field, objectData, serializedData);
		} else if (clazz.equals(byte.class) || clazz.equals(Byte.class)) {
			serializeByte(field, objectData, serializedData);
		} else if (clazz.equals(char.class) || clazz.equals(Character.class)) {
			serializeCharacter(field, objectData, serializedData);
		} else if (clazz.equals(short.class) || clazz.equals(Short.class)) {
			serializeShort(field, objectData, serializedData);
		} else if (clazz.equals(int.class) || clazz.equals(Integer.class)) {
			serializeInteger(field, objectData, serializedData);
		} else if (clazz.equals(long.class) || clazz.equals(Long.class)) {
			serializeLong(field, objectData, serializedData);
		} else if (clazz.equals(float.class) || clazz.equals(Float.class)) {
			serializeFloat(field, objectData, serializedData);
		} else if (clazz.equals(double.class) || clazz.equals(Double.class)) {
			serializeDouble(field, objectData, serializedData);
		} else if (clazz.equals(String.class)) {
			serializeString(field, objectData, serializedData);
		} else if (clazz.equals(Date.class)) {
			serializeDate(field, objectData, serializedData);
		} else if (clazz instanceof ParameterizedType && ((ParameterizedType) clazz).getRawType().equals(ArrayList.class) || (clazz instanceof GenericArrayType && ((GenericArrayType) clazz).getGenericComponentType().equals(ArrayList.class))) {
			serializeArrayList(field, objectData, serializedData);
		} else if ((clazz instanceof ParameterizedType && ((ParameterizedType) clazz).getRawType().equals(HashMap.class)) || (clazz instanceof GenericArrayType && ((GenericArrayType) clazz).getGenericComponentType().equals(HashMap.class))) {
			serializeHashMap(field, objectData, serializedData);
		} else if (isTransportable(clazz)) {
			serializeTransportable(field, objectData, serializedData);
		} else {
			throw new TransportException("not a valid Type. clazz : "+clazz);
		}
	}

	public static boolean isTransportable(Type clazz) {
		boolean isTransportable = false;
		if(clazz.equals(Transportable.class))
		{
			isTransportable = true;
		}
		else if (clazz instanceof Class<?>) {
			Class<?>[] classInterfaces = ((Class<?>) clazz).getInterfaces();
			for (int intfIdx = 0; intfIdx < classInterfaces.length; intfIdx++) {
				if (classInterfaces[intfIdx].equals(Transportable.class)) {
					isTransportable = true;
				}
			}
		}

		return isTransportable;
	}

	protected abstract void onSerializeField(TransportField field, Object serializedData) throws TransportException;

	protected abstract void afterSerializeField(ArrayList<TransportField> fields, Object serializedData) throws TransportException;

	protected abstract void serializeTransportable(TransportField field, Object objectData, Object serializedData) throws TransportException;

	protected abstract void serializeBoolean(TransportField field, Object objectData, Object serializedData) throws TransportException;

	protected abstract void serializeByte(TransportField field, Object objectData, Object serializedData) throws TransportException;

	protected abstract void serializeCharacter(TransportField field, Object objectData, Object serializedData) throws TransportException;

	protected abstract void serializeShort(TransportField field, Object objectData, Object serializedData) throws TransportException;

	protected abstract void serializeInteger(TransportField field, Object objectData, Object serializedData) throws TransportException;

	protected abstract void serializeLong(TransportField field, Object objectData, Object serializedData) throws TransportException;

	protected abstract void serializeFloat(TransportField field, Object objectData, Object serializedData) throws TransportException;

	protected abstract void serializeDouble(TransportField field, Object objectData, Object serializedData) throws TransportException;

	protected abstract void serializeString(TransportField field, Object objectData, Object serializedData) throws TransportException;

	protected abstract void serializeDate(TransportField field, Object objectData, Object serializedData) throws TransportException;

	protected abstract void serializeArrayList(TransportField field, Object objectData, Object serializedData) throws TransportException;

	protected abstract void processArrayList(TransportField field, ArrayList<Object> data, Type genericValueClass, Object serializedData) throws TransportException;

	protected abstract void serializeHashMap(TransportField field, Object objectData, Object serializedData) throws TransportException;

	protected abstract void processHashMap(TransportField field, HashMap<Object, Object> data, Type genericValueClass, Object serializedData) throws TransportException;

	protected Object deserilizedData;

	public Object deserialize(byte[] dataString) throws TransportException {
		Object deserilizedData = null;
		Object serializedData = parseData(dataString);

		if (isTransportableArray(serializedData)) {
			deserilizedData = readTransportableArray(serializedData);
		} else {
			deserilizedData = readTransportable(getTransportable(serializedData));
		}
		return deserilizedData;
	}

	protected abstract Object getTransportable(Object serializedData);

	protected abstract boolean isTransportableArray(Object serializedData) throws TransportException;

	protected abstract Object parseData(byte[] dataString) throws TransportException;

	protected abstract Class<?> readClass(Object serializedData) throws TransportException;

	protected String readClassName(Class<?> clazz) throws TransportException {
		return namesClass.get(clazz);
	}

	protected abstract Object readTransportableArray(Object serializedData) throws TransportException;

	protected abstract Object readTransportable(Object serializedData) throws TransportException;

	protected void deserializeField(TransportField field, Object objectData, Object deserializedObject) throws TransportException {
		try {
			Type clazz = field.getType();

			if (!(clazz instanceof ParameterizedType) && !(clazz instanceof GenericArrayType) && ((Class<?>) clazz).isArray()) {
				clazz = ((Class<?>) clazz).getComponentType();
			} else if (clazz instanceof GenericArrayType) {
				clazz = ((GenericArrayType) clazz).getGenericComponentType();
			}

			if (clazz.equals(boolean.class) || clazz.equals(Boolean.class)) {
				deserializeBoolean(field, objectData, deserializedObject);
			} else if (clazz.equals(byte.class) || clazz.equals(Byte.class)) {
				deserializeByte(field, objectData, deserializedObject);
			} else if (clazz.equals(char.class) || clazz.equals(Character.class)) {
				deserializeCharacter(field, objectData, deserializedObject);
			} else if (clazz.equals(short.class) || clazz.equals(Short.class)) {
				deserializeShort(field, objectData, deserializedObject);
			} else if (clazz.equals(int.class) || clazz.equals(Integer.class)) {
				deserializeInteger(field, objectData, deserializedObject);
			} else if (clazz.equals(long.class) || clazz.equals(Long.class)) {
				deserializeLong(field, objectData, deserializedObject);
			} else if (clazz.equals(float.class) || clazz.equals(Float.class)) {
				deserializeFloat(field, objectData, deserializedObject);
			} else if (clazz.equals(double.class) || clazz.equals(Double.class)) {
				deserializeDouble(field, objectData, deserializedObject);
			} else if (clazz.equals(String.class)) {
				deserializeString(field, objectData, deserializedObject);
			} else if (clazz.equals(Date.class)) {
				deserializeDate(field, objectData, deserializedObject);
			} else if ((clazz instanceof ParameterizedType && ((ParameterizedType) clazz).getRawType().equals(ArrayList.class)) || (clazz instanceof GenericArrayType && ((GenericArrayType) clazz).getGenericComponentType().equals(ArrayList.class))) {
				deserializeArrayList(field, objectData, deserializedObject);
			} else if ((clazz instanceof ParameterizedType && ((ParameterizedType) clazz).getRawType().equals(HashMap.class)) || (clazz instanceof GenericArrayType && ((GenericArrayType) clazz).getGenericComponentType().equals(HashMap.class))) {
				deserializeHashMap(field, objectData, deserializedObject);
			} else if (isTransportable(clazz)) {
				deserializedTransportable(field, objectData, deserializedObject);
			}
		} catch (Exception e) {
			throw new TransportException(e);
		}
	}

	protected abstract void deserializedTransportable(TransportField field, Object objectData, Object deserializedObject) throws TransportException;

	protected abstract void deserializeBoolean(TransportField field, Object objectData, Object deserializedObject) throws TransportException;

	protected abstract void deserializeByte(TransportField field, Object objectData, Object deserializedObject) throws TransportException;

	protected abstract void deserializeCharacter(TransportField field, Object objectData, Object deserializedObject) throws TransportException;

	protected abstract void deserializeShort(TransportField field, Object objectData, Object deserializedObject) throws TransportException;

	protected abstract void deserializeInteger(TransportField field, Object objectData, Object deserializedObject) throws TransportException;

	protected abstract void deserializeLong(TransportField field, Object objectData, Object deserializedObject) throws TransportException;

	protected abstract void deserializeFloat(TransportField field, Object objectData, Object deserializedObject) throws TransportException;

	protected abstract void deserializeDouble(TransportField field, Object objectData, Object deserializedObject) throws TransportException;

	protected abstract void deserializeString(TransportField field, Object objectData, Object deserializedObject) throws TransportException;

	protected abstract void deserializeDate(TransportField field, Object objectData, Object deserializedObject) throws TransportException;

	protected abstract void deserializeArrayList(TransportField field, Object objectData, Object deserializedObject) throws TransportException;

	protected abstract void deserializeHashMap(TransportField field, Object objectData, Object deserializedObject) throws TransportException;

	protected abstract void readArrayList(TransportField field, Object value, Class<?> genericValueClass, Object deserializedDataObject) throws TransportException;

	protected abstract void readHashMap(TransportField field, Object value, Class<?> genericValueClass, Object deserializedDataObject) throws TransportException;

	/*protected boolean isprimitiveAndHasValue(TransportField field, Object value) {
		boolean isPrimitiveAndhasValue = true;
		
		if (!field.isArray() && field.isPrimitive()) {
			Class<?> clazz = value.getClass();
			if (clazz.equals(Boolean.class)) {
				isPrimitiveAndhasValue = (((Boolean) value).booleanValue() != false);
			} else if (clazz.equals(Byte.class)) {
				isPrimitiveAndhasValue = (((Byte) value).byteValue() != 0);
			} else if (clazz.equals(Character.class)) {
				isPrimitiveAndhasValue = (((Character) value).charValue() != '\0');
			} else if (clazz.equals(Short.class)) {
				isPrimitiveAndhasValue = (((Short) value).shortValue() != 0);
			} else if (clazz.equals(Integer.class)) {
				isPrimitiveAndhasValue = (((Integer) value).intValue() != 0);
			} else if (clazz.equals(Long.class)) {
				isPrimitiveAndhasValue = (((Long) value).longValue() != 0L);
			} else if (clazz.equals(Float.class)) {
				isPrimitiveAndhasValue = (((Float) value).floatValue() != 0.0f);
			} else if (clazz.equals(Double.class)) {
				isPrimitiveAndhasValue = (((Double) value).shortValue() != 0.0d);
			}
		}

		return isPrimitiveAndhasValue;
	}*/

	public static boolean checkTransportableEquals(Object object1, Object object2) {
		boolean toRet = false;
		try {
			if (object1 != null && object2 != null) {
				Class<?> object1Class = object1.getClass();
				Class<?> object2Class = object2.getClass();

				if (object1Class.equals(object2Class)) {
					toRet = true;
					ArrayList<TransportField> fields = classFields.get(object1Class);
					for (int fieldIdx = 0; fieldIdx < fields.size(); fieldIdx++) {
						TransportField field = fields.get(fieldIdx);

						Object thisObjectValue = field.getValue(object1);
						Object otherObjectValue = field.getValue(object2);

						if (thisObjectValue != null && otherObjectValue != null) {
							Type componentType = field.getType();
							if ((componentType instanceof Class<?> && ((Class<?>) componentType).isArray()) || componentType instanceof GenericArrayType) {
								if (componentType instanceof GenericArrayType) {
									Type generivValueClass = ((GenericArrayType) componentType).getGenericComponentType();
									if (generivValueClass instanceof ParameterizedType) {
										generivValueClass = ((ParameterizedType) generivValueClass).getRawType();
										if (generivValueClass.equals(ArrayList.class)) {
											toRet = Arrays.equals((ArrayList[]) thisObjectValue, (ArrayList[]) otherObjectValue);

										} else if (generivValueClass.equals(HashMap.class)) {
											toRet = Arrays.equals((HashMap[]) thisObjectValue, (HashMap[]) otherObjectValue);
										}
									}
								} else {
									componentType = ((Class<?>) componentType).getComponentType();

									if (componentType.equals(Boolean.class) || componentType.equals(boolean.class)) {
										if (field.isPrimitive()) {
											toRet = Arrays.equals((boolean[]) thisObjectValue, (boolean[]) otherObjectValue);
										} else {
											toRet = Arrays.equals((Boolean[]) thisObjectValue, (Boolean[]) otherObjectValue);
										}
									} else if (componentType.equals(Byte.class) || componentType.equals(byte.class)) {
										if (field.isPrimitive()) {
											toRet = Arrays.equals((byte[]) thisObjectValue, (byte[]) otherObjectValue);
										} else {
											toRet = Arrays.equals((Byte[]) thisObjectValue, (Byte[]) otherObjectValue);
										}
									} else if (componentType.equals(Character.class) || componentType.equals(char.class)) {
										if (field.isPrimitive()) {
											toRet = Arrays.equals((char[]) thisObjectValue, (char[]) otherObjectValue);
										} else {
											toRet = Arrays.equals((Character[]) thisObjectValue, (Character[]) otherObjectValue);
										}
									} else if (componentType.equals(Short.class) || componentType.equals(short.class)) {
										if (field.isPrimitive()) {
											toRet = Arrays.equals((short[]) thisObjectValue, (short[]) otherObjectValue);
										} else {
											toRet = Arrays.equals((Short[]) thisObjectValue, (Short[]) otherObjectValue);
										}
									} else if (componentType.equals(Integer.class) || componentType.equals(int.class)) {
										if (field.isPrimitive()) {
											toRet = Arrays.equals((int[]) thisObjectValue, (int[]) otherObjectValue);
										} else {
											toRet = Arrays.equals((Integer[]) thisObjectValue, (Integer[]) otherObjectValue);
										}
									} else if (componentType.equals(Long.class) || componentType.equals(long.class)) {
										if (field.isPrimitive()) {
											toRet = Arrays.equals((long[]) thisObjectValue, (long[]) otherObjectValue);
										} else {
											toRet = Arrays.equals((Long[]) thisObjectValue, (Long[]) otherObjectValue);
										}
									} else if (componentType.equals(Float.class) || componentType.equals(float.class)) {
										if (field.isPrimitive()) {
											toRet = Arrays.equals((float[]) thisObjectValue, (float[]) otherObjectValue);
										} else {
											toRet = Arrays.equals((Float[]) thisObjectValue, (Float[]) otherObjectValue);
										}
									} else if (componentType.equals(Double.class) || componentType.equals(double.class)) {
										if (field.isPrimitive()) {
											toRet = Arrays.equals((double[]) thisObjectValue, (double[]) otherObjectValue);
										} else {
											toRet = Arrays.equals((Double[]) thisObjectValue, (Double[]) otherObjectValue);
										}
									} else if (componentType.equals(String.class)) {
										toRet = Arrays.equals((String[]) thisObjectValue, (String[]) otherObjectValue);
									} else if (isTransportable(componentType)) {
										toRet = Arrays.equals((Object[]) thisObjectValue, (Object[]) otherObjectValue);
									}
								}
							} else if (!thisObjectValue.equals(otherObjectValue)) {
								toRet = false;
							}
						} else {
							toRet = true;
						}

						if (!toRet) {
							System.out.println(field.getName() + " IS NOT CORRECT");
							break;
						}
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			toRet = false;
		}

		return toRet;
	}

	public static boolean checkTransportableArray(Object[] array1, Object[] array2) {
		boolean equals = true;
		for (int i = 0; i < array1.length; i++) {
			equals = (array1[i]).equals(array2[i]);
		}
		return equals;
	}

	public static String getClassName(Class<?> clazz) {
		return namesClass.get(clazz);
	}

	public static Class<?> getClass(String name) {
		return classNames.get(name);
	}
}
